﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Data.SqlClient;
using System.Linq;
using LayeredArchitectureSample.DomainModel;
using LayeredArchitectureSample.Infrastructure;

namespace LayeredArchitectureSample.DataAccess
{
    // TODO: autogenerate at some point
    /// <summary>
    /// </summary>
    /// <remarks> Use TransactionScope to get transactions across several repository operations</remarks>
    public class EntityFrameworkWareRepository : IWareRepository
    {
        private readonly IDatabaseContextFactory _dataContextFactory;

        internal EntityFrameworkWareRepository(IDatabaseContextFactory dataContextFactory)
        {
            if (dataContextFactory == null) throw new ArgumentNullException("dataContextFactory");

            _dataContextFactory = dataContextFactory;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="CannotRemoveEntityException"></exception>
        /// <exception cref="EntityNotFoundException"></exception>
        public void RemoveWare(long id)
        {
            using (LayeredSampleDataModel dataContext = _dataContextFactory.CreateDatabaseContext())
            {
                try
                {
                    Ware wareWithGivenId = GetWareWithGivenIdOrNull(id, dataContext);

                    if (wareWithGivenId == null)
                    {
                        throw new EntityNotFoundException(Resources.CannotRemoveWareHaventFoundIt.FormatWith(id));
                    }

                    dataContext.DeleteObject(wareWithGivenId);

                    dataContext.SaveChanges();
                }
                catch (UpdateException exception)
                {
                    SqlException innerExceptionAsSqlException = exception.InnerException as SqlException;

                    if (innerExceptionAsSqlException != null &&
                        innerExceptionAsSqlException.Number == Constants.Database.ReferencesToObjectDetectedErrorCode)
                    {
                        throw new CannotRemoveEntityException(Resources.FailedToRemoveWareReferencedByAnotherObject, exception);
                    }

                    throw new CannotRemoveEntityException(Resources.FailedToRemoveWare.FormatWith(id), exception);
                }
                catch (EntityException exception)
                {
                    throw new CannotRemoveEntityException(
                        Resources.FailedToRemoveWare.FormatWith(id),
                        exception);
                }
                catch (InvalidOperationException)
                {
                    throw new CannotRemoveEntityException(Resources.FailedToRemoveWareDataIsInconsistent.FormatWith(id));
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="id"></param>
        /// <param name="dataContext"></param>
        /// <returns></returns>
        /// <exception cref="InvalidOperationException">More than one object with given Id</exception>
        private Ware GetWareWithGivenIdOrNull(long id, LayeredSampleDataModel dataContext)
        {
            return (from Ware ware in dataContext.Wares
                    where ware.Id == id
                    select ware).SingleOrDefault();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="code"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentException"></exception>
        /// <exception cref="CannotAddEntityException"></exception>
        public DomainModel.Ware AddNewWare(string code)
        {
            // Validation
            var newWareToBe = new DomainModel.Ware(1, code);

            using (LayeredSampleDataModel dataContext = _dataContextFactory.CreateDatabaseContext())
            {
                var newWare = new Ware() {Code = code};

                dataContext.Wares.AddObject(newWare);

                try
                {
                    dataContext.SaveChanges();
                }
                catch (UpdateException exception)
                {
                    SqlException innerException = exception.InnerException as SqlException;

                    if (innerException != null &&
                        innerException.Number == Constants.Database.UniqueConstraintViolationErrorCode)
                    {
                        throw new CannotAddEntityException(Resources.WareAlreadyExists.FormatWith(code));
                    }
                    else
                    {
                        throw new CannotAddEntityException(Resources.FailedToAddWare, exception);
                    }
                }

                return new DomainModel.Ware(newWare.Id, newWare.Code);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        /// <exception cref="EntityRetrievalException"></exception>
        public IEnumerable<DomainModel.Ware> GetAll()
        {
            using (LayeredSampleDataModel dataContext = _dataContextFactory.CreateDatabaseContext())
            {
                try
                {
                    var allWares = new List<DomainModel.Ware>();

                    foreach (Ware ware in dataContext.Wares)
                    {
                        allWares.Add(new DomainModel.Ware(ware.Id, ware.Code));
                    }

                    return allWares;
                }
                catch (EntityException exception)
                {
                    throw new EntityRetrievalException(Resources.FailedToRetrieveWares, exception);
                }
            }
        }
    }
}